﻿using System;
using System.Linq;

using mvcForum.Core;
using mvcForum.Core.Abstractions.Interfaces;
using mvcForum.Core.Events;
using mvcForum.Core.Interfaces.AddOns;
using mvcForum.Core.Interfaces.Events;
using mvcForum.Core.Specifications;
using SimpleLocalisation;
using mvcForum.Core.Interfaces.Services;
using mvcForum.Core.Interfaces.Data;

namespace mvcForum.AddOns.MultiPost {

	public class MultiPostAddOn : IAntiSpamAddOn, IEventListener<NewPostEvent>, IEventListener<NewTopicEvent> {
		private readonly MultiPostConfiguration addOnConfig;
		private readonly ILogger logger;
		private readonly IRepository<Post> postRepo;
		private readonly IRepository<Topic> topicRepo;
		private readonly IContext context;
		private readonly TextManager textManager;

		public MultiPostAddOn(MultiPostConfiguration addOnConfig, ILogger logger, IRepository<Post> postRepo, IRepository<Topic> topicRepo, IContext context, TextManager textManager) {
			this.addOnConfig = addOnConfig;
			this.logger = logger;
			this.postRepo = postRepo;
			this.topicRepo = topicRepo;
			this.context = context;
			this.textManager = textManager;
		}

		public void Handle(Object payload) {
			if (this.addOnConfig.Enabled) {
				if (payload is NewTopicEvent) {
					this.Handle((NewTopicEvent)payload);
				}
				else if (payload is NewPostEvent) {
					this.Handle((NewPostEvent)payload);
				}
				else {
					throw new ApplicationException("Unknown payload!");
				}
			}
		}

		/// <summary>
		/// Event handler for a new post.
		/// </summary>
		/// <param name="payload"></param>
		public void Handle(NewPostEvent payload) {
			if (this.addOnConfig.Enabled) {
				Post post = this.postRepo.Read(payload.PostId);
				// Not already deleted or quarantined? Should the new post be put in quarantine?
				if ((post.Flag & PostFlag.Deleted) == 0 &&
					(post.Flag & PostFlag.Quarantined) == 0 &&
					this.ShouldQuarantine(post.Author, payload)) {

					// Yes, let's mark it quarantined!
					post.SetFlag(PostFlag.Quarantined);

					this.MarkAsSpam(post, payload);

					this.context.SaveChanges();
				}
			}
		}

		private void MarkAsSpam(Post post, InformationBearingEvent payload) {
			post.EditReason += Environment.NewLine + this.textManager.Get("QuarantineReason", ns: "mvcForum.AddOns.MultiPost", values: new { Interval = this.addOnConfig.Interval, Posts = this.addOnConfig.Posts });

			payload.AddFeedback("QuarantineFeedback", "mvcForum.AddOns.MultiPost", null);
		}

		/// <summary>
		/// Event handler for a new topic.
		/// </summary>
		/// <param name="payload"></param>
		public void Handle(NewTopicEvent payload) {
			if (this.addOnConfig.Enabled) {
				Topic topic = this.topicRepo.Read(payload.TopicId);
				// Not already deleted or quarantined? Should the new topic be put in quarantine?
				if ((topic.Flag & TopicFlag.Deleted) == 0 &&
					(topic.Flag & TopicFlag.Quarantined) == 0 &&
					this.ShouldQuarantine(topic.Author, payload)) {

					// Yes, let's mark it quarantined!
					topic.SetFlag(TopicFlag.Quarantined);

					this.MarkAsSpam(topic.Posts.OrderBy(p => p.Posted).First(), payload);

					this.context.SaveChanges();
				}
			}
		}

		/// <summary>
		/// Method for determining whether or not the new post/topic should be put in quarantine.
		/// </summary>
		/// <param name="author">The author of the post/topic.</param>
		/// <param name="e">The event.</param>
		/// <returns>True if the post/topic should be quarantined, false otherwise.</returns>
		private Boolean ShouldQuarantine(ForumUser author, InformationBearingEvent e) {
			Boolean quarantine = false;

			// Do we do interval quarantine??
			if (this.addOnConfig.Posts > 0 && this.addOnConfig.Interval > 0) {
				// Yes!
				// How many posts have the author posted in the last (interval) minutes??
				Int32 posts = this.postRepo.ReadMany(new PostSpecifications.RecentlyByAuthor(author, this.addOnConfig.Interval)).Count();
				// The number of posts bigger than the max. allowed posts??
				quarantine = posts > this.addOnConfig.Posts;
				// Is the post put in quarantined?
			}
			return quarantine;
		}

		public Boolean CanRunAsync {
			get {
				return false;
			}
		}

		public Byte Priority {
			get {
				return (Byte)EventPriority.Medium;
			}
		}

		public Boolean UniqueEvent {
			get {
				return false;
			}
		}
	}
}